#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <functional>

#include <cerrno>
#include <cstring>

#include "mydll/Sock.hpp"
#include "mydll/Util.hpp"
#include "Epoller.hpp"
#include "mydll/Protocol.hpp"


//Reactor服务器
namespace Epoll
{
    using namespace TCP;
    using namespace protocol;
    class Server;
    class Connection;

    class Connection //文件描述符缓冲区
    {
        using callback_t = std::function<void(Connection*)>; //事件处理函数 针对每一个fd设置独立的事件处理函数 避免服务器中繁杂的代码处理
    public:
        Connection(const int& fd,int events,const std::string& clientip,const uint16_t& clientport)
            :_fd(fd)
            ,_events(events)
            ,_clientip(clientip)
            ,_clientport(clientport)
        {}

        //注册方法
        void Register(callback_t recver,callback_t sender,callback_t excepter)
        {
            _recver = recver;
            _sender = sender;
            _excepter = excepter;
        }
    public:
        //sock信息
        int _fd;
        std::string _inbuffer;
        std::string _outbuffer;

        //事件处理函数
        callback_t _recver; //读
        callback_t _sender; //写
        callback_t _excepter; //异常

        //用户信息
        std::string _clientip;
        uint16_t _clientport;

        //记录事件
        int _events;

        //回指指针 用于设置读写
        Server* ser;
    };



    class Server
    {
        using func_t = std::function<Response(const Request&)>;
        const static int gnum = 64;
        const static int defaultport = 8088;
        const static int bsize = 1024; //读取缓冲区大小
    public:
        Server(const func_t& func,const uint16_t& port = defaultport)
            :_port(port)
            ,_ss(_port)
            ,_func(func)
        {}

        void Init()
        {
            _ss.Socket();
            _ss.Bind();
            _ss.Listen();

            //创建Epoll文件file
            _ep.Create();

            //将服务器fd设置进入epoll 使用默认ip和端口即可
            //AddConnection(_ss.GetSockFd(),EPOLLIN); //LT模式
            AddConnection(_ss.GetSockFd(),EPOLLIN | EPOLLET);  //ET模式 其读写接口必须为非阻塞
        }

        //事件派发器
        void Dispatcher()
        {

            int timeout = -1; //阻塞式监视
            //进行循环事件派发
            while(true)
            {
                HandlerEvents(timeout);
            }
        }

        //读写设置
        bool EnableReadWrite(Connection *conn, bool readable, bool writeable)
        {
            conn->_events = (readable?EPOLLIN:0) | (writeable?EPOLLOUT:0) | EPOLLET; //根据参数 设置合适的标记
            return _ep.AddModEvent(conn->_fd,conn->_events,EPOLL_CTL_MOD);
        }

        ~Server() 
        {
            _ss.Close_Sockfd();
            _ep.Close();
        }

    protected:
        //扫描就绪事件 进行事件派发
        void HandlerEvents(int timeout)
        {
            int n = _ep.Wait(_revs,gnum,timeout);
            for(int i = 0;i<n;++i) //n<=0都不会进入循环
            {
                int fd = _revs[i].data.fd;
                uint32_t event = _revs[i].events;

                //我们不想过多的在很多地方调用_excepter函数 显得非常凌乱
                //我们将_excepter函数出现的情况 固定在_recver和_sender函数中 只有这两个函数触发_excepter
                if((event & EPOLLERR) || (event & EPOLLHUP)) event |= (EPOLLIN | EPOLLOUT); //标记读写事件 从中触发_excepter
                //_excepter函数将处理异常 并关闭sockfd


                //一旦触发异常 可能这个fd和链接资源_con就被删除了 我们使用[]访问 需要保证一定存在 需要检测安全性
                if((event & EPOLLIN) && (ConnIsExists(fd))) _con[fd]->_recver(_con[fd]); //读事件
                if((event & EPOLLOUT) && (ConnIsExists(fd))) _con[fd]->_sender(_con[fd]);// 写事件
                //if((event & EPOLLERR) || (event & EPOLLHUP)) _con[fd]->_excepter(_con[fd]);//异常事件 fd异常或对端关闭连接
            }
        }

        //添加一个新连接-连接管理器
        void AddConnection(const int& fd,const int& events,const std::string ip = "127.0.0.1",const uint16_t& port = defaultport)
        {
            //1.判断是否为ET模式 将文件描述符设置为非阻塞
            if(events & EPOLLET) Util::SetNonBlock(fd);
            //2.添加缓冲区
            Connection* conn = new Connection(fd,events,ip,port);
            if(fd == _ss.GetSockFd()) conn->Register(std::bind(&Server::Accept,this,std::placeholders::_1),nullptr,nullptr);

            else conn->Register(std::bind(&Server::Recver,this,std::placeholders::_1),
                                std::bind(&Server::Sender,this,std::placeholders::_1),
                                std::bind(&Server::Excepter,this,std::placeholders::_1));


            conn->ser = this; //设置回指指针
            //3.将服务器sockfd放入监听 并分配专属缓冲区
            _con[fd] = conn;
            //4.将文件描述符设置进epoll
            if(!_ep.AddModEvent(fd,events,EPOLL_CTL_ADD)) 
            {
                delete conn;
                _con.erase(fd);
                logMessage(Fatal,"AddConnection for AddEvent error! fd:%d",fd);
            }
            logMessage(Debug,"AddConnection success! fd:%d client:[%s|%d]",fd,ip.c_str(),port);

        }


        void Accept(Connection* conn)
        {
            //LT模式下读取一次结束
            //ET模式下循环读取
            do
            {
                std::string client_ip;
                uint16_t client_port;
                int err = 0;
                int client_sock = _ss.Accept(&client_ip,&client_port,&err);
                if(client_sock > 0)
                {
                    //服务器默认连接的文件描述符一开始是读取(向服务器写内容)
                    // if(!_ep.AddEvent(client_sock,EPOLLIN))
                    // {
                    //     close(client_sock); //加入失败则关闭连接
                    //     logMessage(Warning,"client AddEvent error!");
                    //     return;
                    // }
                    // Connection* conn = new Connection(_ss.GetSockFd());
                    // _con[client_sock] = conn;
                    
                    //AddConnection(client_sock,EPOLLIN,client_ip,client_port);
                    logMessage(Debug, "%s:%d 已经连上了服务器了", client_ip.c_str(), client_port);
                    AddConnection(client_sock,EPOLLIN | EPOLLET,client_ip,client_port);
                    continue;
                }
                else
                {
                    if(err == EAGAIN || err == EWOULDBLOCK) break;//读完了
                    else if(err == EINTR) continue;
                    else
                    {
                        logMessage(Warning,"errstring : %s, errcode: %d", strerror(err), err);
                        continue; //一个文件描述符出问题 不能影响后续读取
                    }
                }

            } while (conn->_events & EPOLLET);
            (void*)conn;
        }

        void HanderRequest(Connection* conn)
        {
            bool quit = false;
            //持续 序列化-反序列化
            while(!quit)
            {
                std::string requestStr;
                //1.提取完整报文
                int n = ParsePackage(_con[conn->_fd]->_inbuffer,&requestStr);
                //进行文本分析 读到一个完整报文 n>0
                if(n > 0) 
                {
                    //2.提取有效载荷
                    requestStr = RemoveHeader(requestStr,n);//去掉报头
                    //3.反序列化
                    Request req; //构建请求
                    req.Deserialize(requestStr); //反序列化 保证requestStr是一个完整的请求
                    //4.业务处理
                    Response resp = _func(req); //处理请求

                    //5.序列化
                    std::string responseStr;
                    resp.Serialize(&responseStr);

                    //6.加报头
                    responseStr = AddHeader(responseStr);

                    //7.写入输出缓冲区
                    conn->_outbuffer += responseStr;
                }
                else quit = true; //当没有报文或者报文不完整 则结束处理
            }

        }

        bool RecverHelper(Connection* conn)
        {
            bool safe = true;
            do //一轮对一个文件描述符进行完整读取
            {
                char buf[bsize] = {0}; //这里仍然无法保证一个应用层报文能读完整
                int rsize = recv(conn->_fd,buf,sizeof(buf)-1,0);
                if(rsize > 0)
                {
                    buf[rsize] = '\0';
                    _con[conn->_fd]->_inbuffer += buf;
                    //logMessage(Debug, "inbuffer: %s, [%d]", conn->_inbuffer.c_str(), conn->_fd);
                    
                }
                else if(rsize == 0) //对端断开连接
                {
                    conn->_excepter(conn); //异常处理 关闭fd
                    safe = false; //一旦出现异常情况 不需要再处理报文
                    break;
                }
                else 
                {
                    if(errno == EAGAIN || errno == EWOULDBLOCK)  break;
                    else if(errno == EINTR) continue;
                    else //其他情况也是异常处理 关闭fd
                    {
                        conn->_excepter(conn); //异常处理 关闭fd
                        safe = false; //一旦出现异常情况 不需要再处理报文
                        break;
                    }
                }
            }while(conn->_events & EPOLLET); //如果是ET工作模式则循环读取 非阻塞
            return safe;
        }

        //处理读取
        void Recver(Connection* conn)
        {
            if(!RecverHelper(conn)) return; //读取完整报文 如果读取出现异常 则结束读取

            HanderRequest(conn); //读取完成后处理报文

            //如果输出缓冲区有报文则直接输出(一般我们在面对写入的时候，直接写入，没写完，才交给epoll!)
            if(!conn->_outbuffer.empty()) conn->_sender(conn);
        }

        //处理写入
        void Sender(Connection* conn)
        {
            bool safe = true;
            do
            {
                int n = send(conn->_fd,conn->_outbuffer.c_str(),conn->_outbuffer.size(),0);
                if(n > 0)
                {
                    conn->_outbuffer.erase(0,n); //删除已经发送的n个字符
                    if(conn->_outbuffer.empty()) break; //如果写完了 则退出
                }
                else
                {
                    if(errno == EAGAIN || errno == EWOULDBLOCK) break; //如果写入缓冲区已满 或其他原因无法发送 则暂停发送
                    else if(errno == EINTR) continue; //如果因为信号原因暂停 则继续
                    else //异常情况
                    {
                        safe = false; //如果异常 则退出 不在执行epoll标记回写
                        conn->_excepter(conn); 
                        break;
                    }
                }
            }while(conn->_events & EPOLLET); //如果是ET工作模式则循环写入 非阻塞
            
            if(!safe) return; //如果异常退出 后面回写设置不在执行

            //无论是写完了退出 还是因为缓冲区满了等非异常原因退出 则设置下一次回写策略 一次没写完才希望epoll继续关注缓冲区是否还能写入
            if(conn->_outbuffer.empty()) EnableReadWrite(conn,true,false); //如果全部发送 则关闭写入
            else EnableReadWrite(conn,true,true); //如果没有发完 则继续发送
        }

        //处理异常
        void Excepter(Connection* conn)
        {
            logMessage(Debug, "Excepter...done, fd: %d, clientinfo: [%s:%d]", conn->_fd, conn->_clientip.c_str(), conn->_clientport);

            //1.取消Epoll检测
            _ep.DelEvent(conn->_fd);
            //2.从map中删除kv
            _con.erase(conn->_fd);
            //3.关闭fd
            close(conn->_fd);
            //4.释放空间
            delete conn;

        }

        bool ConnIsExists(int fd)
        {
            return _con.find(fd) != _con.end();
        }

    private:
        uint16_t _port;
        SockServer _ss;
        Epoller _ep;
        struct epoll_event _revs[gnum]; //事件管理数组 接受每一个fd所就绪的事件
        func_t _func; //任务处理函数
        std::unordered_map<int,Connection*> _con; //建立文件描述符和缓冲区的映射
    };
}

//连接管理
//为每个连接维护一个定时器，记录最近访问时间，长期没有访问的进行关闭

//扩展
//1.线程池版本
//将_func业务处理剥离Reactor线程，Reactor只负责事件派发+读写，业务处理交给线程池 实现同步和异步
//前摄式设计：Reactor只负责事件派发，线程池负责读写和业务处理
//2.多线程版本
//多线程下，_con成为临界资源，为了避免因为线程安全导致报文写入乱序问题，一个fd和_con一定只能有一个线程来进行管理
//创建主线程拥有listen套接字，创建其余线程也有Reactor模式，但没有自己的listen套接字，次线程只负责管理一部分文件描述符
//主线程负责监听客户端连接，一旦有客户端连接则获得文件描述符将其放入队列中，次线程从队列中拿文件描述符自己维护(需要改Epoll服务器的Init和Sock.hpp逻辑)
//这种情况下，主线程负责连接派发，次线程按照一定顺序取fd，做到负载均衡
//3.多进程版本
//主进程创建listen套接字和服务器，创建子进程继承父进程的listen套接字，子进程Epoll关闭对listen套接字的监听
//只有主线程才能监听，接入基于管道的线程池，当主线程监听到有连接时，自己不accept，而是通过管道唤醒一个子进程
//由任意一个子进程进行监听获取链接，然后该子进程进行维护
//父进程是master，子进程是slaver，当链接数量大时可以多创建几个进程，当链接数量少时可以关闭一些进程(通过管道)
//4.多进程多线程都可以用的方法(不太推荐)
//父进程(父线程)在创建listen套接字和服务器后，创建子进程(子线程)，父子进程(线程)地位相等，子进程(线程)自己new一个服务器对象继承父进程的listen套接字加入
//自己的epoll中，然后对accept进行加锁(信号量)，一旦有链接到来，所有进程(线程)便会争抢套接字，拿到锁的争抢成功，自己维护起来
//为了保证一个线程一直争抢导致不能负载均衡，可以加一些策略